package com.example.demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import com.example.demo.config.enter.CustomSecuityError;
import com.example.demo.config.login.CustomAuthenticationFilter;
import com.example.demo.config.login.CustomFailureHandler;
import com.example.demo.config.login.CustomSuccessHandler;
import com.example.demo.config.logout.CustomLogoutHander;
import com.example.demo.config.logout.CustomLogoutSuccessHander;
import com.example.demo.service.UserServices;

/**
 * 安全策略
 */
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	@Bean
	UserDetailsService userService() {
		return new UserServices();
	}

	/**
	 * 配置验证机制，可以在这里修改不同的验证
	 * 
	 * @param auth
	 * @throws Exception
	 */
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		/**
		 * 这个Service配置会使下面的配置失效，无法抛出User为空的异常
		 */
		// auth.userDetailsService(userService());
		// 添加下面新的配置
		auth.authenticationProvider(authenticationProvider());
		/**
		 * 可以添加一个自定义的验证机制
		 */
		// auth.authenticationProvider(new CustomAuthenticationProvider());
	}

	/**
	 * 关于authenticationProvider
	 * 1、验证过程中，可以增加多个authenticationProvider，来完成不同的验证工作，auth.authenticationProvider(@Bean).authenticationProvider(@Bean);
	 * 2、根据验证的先后顺序，需要注意authenticationProvider设置的先后顺序
	 * 3、如果抛出异常，即可停止的话，不能抛出AuthenticationException
	 * 4、每个authenticationProvider都要配置一个UserDetailsService的实现类，配置userService();，自定义查询用户那个
	 * 5、如果不使用DaoAuthenticationProvider或要重新配置，就直接使用auth.authenticationProvider()方法，不要使用auth.userDetailsService()
	 */

	/**
	 * 在这里配置认证管理，不然无法正常抛出，UserNot为空的异常
	 * 
	 * @return
	 */
	@Bean
	DaoAuthenticationProvider authenticationProvider() {
		DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
		// 设置是否隐藏用户为空时的异常
		provider.setHideUserNotFoundExceptions(false);
		// user服务类
		provider.setUserDetailsService(userService());
		// 设置密码类型
		provider.setPasswordEncoder(passwordEncoder());
		return provider;
	}

	/**
	 * 静态资源映射问题
	 * 
	 * @param web
	 * @throws Exception
	 * 
	 * @Override public void configure(WebSecurity web) throws Exception {
	 *           web.ignoring().antMatchers("/global/**");
	 *           web.ignoring().antMatchers("/error/**"); }
	 */

	/**
	 * spring
	 * security会默认拦截/login这个请求，进行Authentication验证，过了就分配session，不分配cookie，可以自行开启
	 */

	/**
	 * authorizeRequests是指定制请求的一些东西
	 * 
	 * @param http
	 * @throws Exception
	 */
	@Override
	protected void configure(HttpSecurity http) throws Exception {

		/**
		 * 规则 /admin/**:请求需要ROLE_ADMIN权限 /user/**:需要ROLE_USER权限
		 * /swagger-ui.html:是为了保护API接口文档被发现 /login:解除对login页面的限制 其它请求随意
		 */
		http.authorizeRequests().antMatchers("/admin/**").access("hasAnyRole('ROLE_ADMIN')").antMatchers("/user/**")
				.access("hasAnyRole('ROLE_USER','ROLE_ADMIN')").antMatchers("/login").permitAll().anyRequest()
				.permitAll();

		/**
		 * 思路： 1.用户统一登陆，登陆后根据用户角色进行权限控制 2.目前，admin需要ROLE_ADMIN，权限
		 */

		// 跨站
		http.csrf().disable();

		// security的异常处理
		http.exceptionHandling().authenticationEntryPoint(new CustomSecuityError());

		/**
		 * sup 这里改用自定义登陆了，CustomAuthenticationFilter具体在这里
		 * <p>
		 * sup 登陆，登陆请求暂时用/login，可能会更改，ajaxrequest是重新定义了，请求后，
		 */
		// http.formLogin().loginPage("/login").loginProcessingUrl("/login");

		/**
		 * s 这里不用原有的登陆logout，或login?logout，改用/api/logout方式，
		 * <p>
		 * s 但其实默认的注销方式还是存在，因为，因为默认的注销返回的是/login，
		 * <p>
		 * s 找不到/login啊啊啊啊啊，睿智的spring，把LogoutConfig写为了final
		 */
		// http.logout().logoutUrl("/api/logout");

		// 添加自定义登陆拦截器
		http.addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);

		// 新定义的注销处理
		http.logout().addLogoutHandler(new CustomLogoutHander()).logoutSuccessHandler(new CustomLogoutSuccessHander());

		// cookie自动登陆
		http.rememberMe().tokenValiditySeconds(60 * 60 * 24 * 7);
	}

	// 登陆时，成功返回，失败返回
	@Bean
	CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
		CustomAuthenticationFilter filter = new CustomAuthenticationFilter();
		// 拦截login的请求，和请求类型
		filter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/login", "POST"));
		filter.setAuthenticationSuccessHandler(new CustomSuccessHandler());
		filter.setAuthenticationFailureHandler(new CustomFailureHandler());

		// 这句很关键，重用WebSecurityConfigurerAdapter配置的AuthenticationManager，不然要自己组装AuthenticationManager
		filter.setAuthenticationManager(authenticationManagerBean());
		return filter;
	}

	/**
	 * 题外话，如果搭自己的oauth2的server，需要让spring security
	 * oauth2共享同一个AuthenticationManager（源码的解释是这样写可以暴露出这个AuthenticationManager，也就是注册到spring
	 * ioc）
	 */
	@Override
	@Bean // share AuthenticationManager for web and oauth
	public AuthenticationManager authenticationManagerBean() throws Exception {
		return super.authenticationManagerBean();
	}

	// 关闭加密，开发中不推荐
	@Bean
	public static NoOpPasswordEncoder passwordEncoder() {
		return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
	}
}
